import os
import sys
from pathlib import Path
from typing import Iterator


class FindFilesMixin:

    @staticmethod
    def find_files(file_name, start_dir=Path('')):
        """
        在项目中查找指定文件

        args:
            file_name: 要查找的文件名 (如 "config.txt")
            start_dir: 开始搜索的目录 (默认为当前目录)

        return:
            找到的文件绝对路径列表
        """
        found_files = []

        # 遍历目录树
        for root, _, files in os.walk(start_dir):
            if file_name in files:
                # abs_path = Path(os.path.abspath(os.path.join(root, file_name)))
                abs_path = Path(root) / file_name
                abs_path = abs_path.resolve()
                # 将找到的文件路径转换为绝对路径并添加到结果列表
                found_files.append(abs_path)

        if len(found_files) > 1:
            raise ValueError(f"查找到多个人间")
        if len(found_files) == 1:
            return found_files[0]

        return found_files

find_files = FindFilesMixin().find_files

def find_dotenv(
    filename: str = ".env",
    raise_error_if_not_found: bool = False,
    usecwd: bool = False,
) -> str:
    """
    在逐渐向上的目录层级中搜索指定的文件

    find_dotenv 函数设计为在逐渐向上的目录层级中搜索指定的文件（默认为 .env）。这种设计是出于几个考虑：
    项目结构约定：在许多项目中，配置文件（如 .env）通常被放置在项目的根目录或靠近根目录的位置，而不是嵌套在深层子目录中。因此，向上查找更符合这些项目的结构约定。

    避免不必要的搜索：向下遍历整个目录树可能会非常耗时，尤其是在大型项目中。向上查找可以更快地定位到可能的配置文件位置，而无需遍历所有子目录。

    简化使用：向上查找使得开发者无需关心当前工作目录或脚本位置，只需确保配置文件位于调用 find_dotenv 的文件路径之上即可。

    args:
        filename: 要查找的文件名，默认为".env"
        raise_error_if_not_found: 如果为True，文件未找到时抛出错误；否则返回空字符串
        usecwd: 如果为True，强制使用当前工作目录作为搜索起点

    return:
        如果找到则返回文件的完整路径，否则返回空字符串
    """

    def _is_interactive():
        """判断当前是否运行在交互式环境中(如REPL或IPython notebook)"""
        try:
            main = __import__("__main__", None, None, fromlist=["__file__"])
        except ModuleNotFoundError:
            return False
        return not hasattr(main, "__file__")

    def _is_debugger():
        """判断当前是否在调试器中运行"""
        return sys.gettrace() is not None

    def _walk_to_root(path: str) -> Iterator[str]:
        """
        Yield directories starting from the given directory up to the root
        产生从给定目录开始直到根目录的目录
        """
        if not os.path.exists(path):
            raise IOError("未找到起始路径")

        if os.path.isfile(path):
            path = os.path.dirname(path)

        last_dir = None
        current_dir = os.path.abspath(path)
        while last_dir != current_dir:
            yield current_dir
            parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))
            last_dir, current_dir = current_dir, parent_dir

    # 确定搜索的起始路径
    if usecwd or _is_interactive() or _is_debugger() or getattr(sys, "frozen", False):
        # 在交互式环境、调试器中或打包成可执行文件时，使用当前工作目录
        path = os.getcwd()
    else:
        # 对于普通.py文件，通过调用栈找到最外层调用文件所在的目录
        frame = sys._getframe()
        current_file = __file__

        # 向上遍历调用栈，直到找到不是当前文件的调用者
        while frame.f_code.co_filename == current_file or not os.path.exists(
            frame.f_code.co_filename
        ):
            assert frame.f_back is not None
            frame = frame.f_back
        frame_filename = frame.f_code.co_filename
        path = os.path.dirname(os.path.abspath(frame_filename))

    # 从起始路径开始向上搜索文件
    for dirname in _walk_to_root(path):
        check_path = os.path.join(dirname, filename)
        if os.path.isfile(check_path):
            return check_path

    # 拓展：如果没有找到文件，尝试在当前目录下查找
    file_path = find_files(filename, Path(path))
    if file_path:
        return str(file_path)

    # 文件未找到时的处理
    if raise_error_if_not_found:
        raise IOError("File not found")

    return ""


if __name__ == '__main__':
    # 查找当前目录及其子目录中的所有 config.ini 文件
    config_files = find_files(".env.mysql")

    print(config_files)

    # 打印找到的文件
    # for file_path in config_files:
    #     print(f"找到配置文件: {file_path}")

    # 从特定目录开始查找
    # docs = find_files("README.md", start_dir="")